﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Represents a hierarchical two-level collection of keys and values.
    /// </summary>
    /// <typeparam name="T2ndLevelKey">The type of the highest level key in the dictionary.</typeparam>
    /// <typeparam name="T1stLevelKey">The type of the lower level key in the dictionary.</typeparam>
    /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
    public sealed class LevelTwoDictionary<T2ndLevelKey, T1stLevelKey, TValue> : ICloneable
    {
        #region Fields.

        IDictionary<T2ndLevelKey, IDictionary<T1stLevelKey, TValue>> l2Dictionary = new Dictionary<T2ndLevelKey, IDictionary<T1stLevelKey, TValue>>();

        #endregion

        #region CTor.

        /// <summary>
        /// Initializes a new instance of <b>LevelTwoDictionary</b> class.
        /// </summary>
        public LevelTwoDictionary()
        { 
        }

        #endregion

        #region Indexer.

        /// <summary>
        /// Gets or sets the value associated with the specified keys.
        /// </summary>
        /// <param name="l2key">Highest level key in the dictionary.</param>
        /// <param name="l1key">Lower level key in the dictionary.</param>
        /// <returns>The value associated with the specified keys. If any of the specified keys is not found,
        /// <b>null</b> is returned, and a <b>set</b> operation creates a new element with the specified keys.</returns>
        public TValue this[T2ndLevelKey l2key, T1stLevelKey l1key]
        {
            get
            {
                TValue val = default(TValue);
                if (this.l2Dictionary.ContainsKey(l2key))
                {
                    IDictionary<T1stLevelKey, TValue> l1Dictionary = this.l2Dictionary[l2key];
                    if (l1Dictionary.ContainsKey(l1key))
                        val = l1Dictionary[l1key];
                }

                return val;
            }
            set
            {
                IDictionary<T1stLevelKey, TValue> l1Dictionary;
                if (this.l2Dictionary.ContainsKey(l2key))
                {
                    l1Dictionary = this.l2Dictionary[l2key];
                }
                else
                {
                    l1Dictionary = new Dictionary<T1stLevelKey, TValue>();
                    this.l2Dictionary.Add(l2key, l1Dictionary);
                }

                if (l1Dictionary.ContainsKey(l1key))
                    l1Dictionary[l1key] = value;
                else
                    l1Dictionary.Add(l1key, value);
            }
        }

        #endregion

        #region Methods.

        /// <summary>
        /// Removes all keys and values below the specified 2nd level key.
        /// </summary>
        /// <param name="l2key">Highest level key in the dictionary.</param>
        /// <returns><b>true</b> if the element is successfully found and removed; otherwise, <b>false</b>. 
        /// This method returns <b>false</b> if any of the specified keys is not found in the dictionary.</returns>
        public bool Remove(T2ndLevelKey l2key)
        {
            return this.l2Dictionary.Remove(l2key);
        }

        /// <summary>
        /// Removes the value with the specified keys from the dictionary.
        /// </summary>
        /// <param name="l2key">Highest level key in the dictionary.</param>
        /// <param name="l1key">Lower level key in the dictionary.</param>
        /// <returns><b>true</b> if the element is successfully found and removed; otherwise, <b>false</b>. 
        /// This method returns <b>false</b> if any of the specified keys is not found in the dictionary.</returns>
        public bool Remove(T2ndLevelKey l2key, T1stLevelKey l1key)
        {
            if (this.l2Dictionary.ContainsKey(l2key))
                return this.l2Dictionary[l2key].Remove(l1key);
            else
                return false;
        }

        /// <summary>
        /// Removes all keys and values from the dictionary. 
        /// </summary>
        public void Clear()
        {
            this.l2Dictionary.Clear();
        }

        /// <summary>
        /// Gets first level key-value pairs below the specified 2nd level key.
        /// </summary>
        /// <param name="l2key">Highest level key in the dictionary.</param>
        /// <returns>Dictionary. <b>null</b> if the specified 2nd level key does not exist.</returns>
        public IDictionary<T1stLevelKey, TValue> Get1stLevelDictionary(T2ndLevelKey l2key)
        {
            if (this.l2Dictionary.ContainsKey(l2key))
                return this.l2Dictionary[l2key];
            else
                return null;
        }

        /// <summary>
        /// Copies all entries from the given dictionary into the current instance.
        /// </summary>
        /// <param name="source">Another dictionary.</param>
        /// <remarks>If a key-combination already exists in the current instance, it will be overwritten.</remarks>
        public void CopyFrom(LevelTwoDictionary<T2ndLevelKey, T1stLevelKey, TValue> source)
        {
            foreach (KeyValuePair<T2ndLevelKey, IDictionary<T1stLevelKey, TValue>> l2SourceEntry in source.l2Dictionary)
            {
                T2ndLevelKey l2key = l2SourceEntry.Key;
                IDictionary<T1stLevelKey, TValue> l1SourceDictionary = l2SourceEntry.Value;
                foreach (KeyValuePair<T1stLevelKey, TValue> l1KeyAndValue in l1SourceDictionary)
                    this[l2key, l1KeyAndValue.Key] = l1KeyAndValue.Value;
            }
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance. 
        /// </summary>
        /// <returns>A new object that is a copy of this instance.</returns>
        public object Clone()
        {
            LevelTwoDictionary<T2ndLevelKey, T1stLevelKey, TValue> clone = new LevelTwoDictionary<T2ndLevelKey, T1stLevelKey, TValue>();
            clone.CopyFrom(this);
            return clone;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets the number of elements in the highest (2nd) level.
        /// </summary>
        public int Count
        {
            get { return this.l2Dictionary.Count; }
        }

        #endregion
    }
}